﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using TangoGames.Scene;
using TangoGames.Base;
using incubation_xna3d.Config;

namespace incubation_xna3d
{
    public class Soldier: AnimatedModel,ICharacter,ITurnElement
    {
        #region Fields

        /// <summary>
        /// Gerenciador da cena corrente
        /// </summary>
        private ISceneManager sceneManager;

        /// <summary>
        /// Ações
        /// </summary>
        public enum Actions { Parado, Andando, Apanhando, Atirando }

        /// <summary>
        /// Estado atual
        /// </summary>
        Actions state;

        /// <summary>
        /// Variaveis para controle de movimentação
        /// </summary>
        float velocity = 0.4f;
        Vector3 destination =  new Vector3() ;
        Vector3 walkdirection = new Vector3();

        /// <summary>
        /// Pontos de movimento do Soldado
        /// </summary>
        private int movementPoints = 5 ;

        private const int ATTACK_RANGE = 150;

        /// <summary>
        /// Posição no tabuleiro
        /// </summary>
        Point boardPosition;

        /// <summary>
        /// Esta inativo
        /// </summary>
        bool isIdle = false;

        /// <summary>
        /// Esta morto
        /// </summary>
        bool isDead = false;

        /// <summary>
        /// Modelo da arma
        /// </summary>
        private Model gunModel;

        /// <summary>
        /// matriz word da arma
        /// </summary>
        private Matrix gunWorld = Matrix.Identity ;

        /// <summary>
        /// pode desenha a arma
        /// </summary>
        private bool canGunDraw = true;

        #endregion        

        #region Properties

        /// <summary>
        /// Posição no tabuleiro
        /// </summary>
        public Point BoardPosition { get { return boardPosition; } set { boardPosition = value; } }

        /// <summary>
        /// Caixa de colisão
        /// </summary>
        public BoundingBox CollisionBox { get { return ModelBoundingBox; } }

        /// <summary>
        /// Gerenciador da cena corrente
        /// </summary>
        public ISceneManager SceneManager { 
            get { return sceneManager; } 
            set { sceneManager = value; } }

        /// <summary>
        /// Estado atual
        /// </summary>
        public Actions State { get { return state; } }

        #endregion

        #region Construction

        public Soldier(Game game, BasicCamera camera)
            : base(game, camera, game.Content.Load<Model>("Models/Swat/SoldadoBase"), "Models/Swat/CollisionSpheres")
        {
            //monstra as esferas de colisão
            //this.ShowSpheres = true;

            this.Scale = 0.1f;

            this.Position = new Vector3(0.0f, 0.0f, 0.0f);

            this.LoadAnimation("Models/Swat/SoldadoAnimadoTeste");

            this.LoadAnimation("Models/Swat/Idle");

            this.LoadAnimation("Models/Swat/Andando");

            this.LoadAnimation("Models/Swat/SoladadoAndando");

            this.LoadAnimation("Models/Swat/Mirando");

            this.LoadAnimation("Models/Swat/morto");

            //lista de animações
            //foreach (String b in skinningData.AnimationClips.Keys)
            //{
            //    Console.WriteLine("Soldado " + b);
            //}

            Idle();

            gunModel = game.Content.Load<Model>("Models/Swat/scarl");

            canGunDraw = true;

            //animationPlayer.Stop();

            //lista o nome dos ossos
            foreach (KeyValuePair<string, int> b in skinningData.BoneIndices)
            {
                Console.WriteLine(b.Key);
            }

        }

        #endregion

        #region Interface Caracter

        public bool isVisible(ICharacter other) {
            bool result = true;

            Vector3 olho = this.Position + new Vector3(0, this.modelBoundingBox.Max.Y / 2, 0);
            Vector3 olhoInimigo = (other.CollisionBox.Max + other.CollisionBox.Min) / 2;

            // Create a ray from the near clip plane to the far clip plane.
            Vector3 direction = olhoInimigo - olho;
            float distanciaInimigo = direction.Length();

            direction.Normalize();

            Ray ray = new Ray(olho, direction);

            foreach (GameObject3D obj in sceneManager.Stage.StageModels.Values)
            {
                Nullable<float> distance = ray.Intersects(obj.ModelBoundingBox);
                if (distance <= distanciaInimigo)
                {
                    result = false;
                    break;
                }
            }
            return result;
        }

        /// <summary>
        /// Atualiza posição do tabuleiro para posição real
        /// </summary>
        /// <param name="pathMap"></param>
        public void UpdateBoardPosition(IPathMap pathMap)
        {
            Point ini = pathMap.ReversePoint(new Vector2(Position.X, Position.Z));
            Vector2 inireal = pathMap.ConvertPoint(ini);
            boardPosition = ini;
        }

        /// <summary>
        /// centraliza posição no quadrado do tabuleiro
        /// </summary>
        /// <param name="pathMap"></param>
        public void CenterPositionBoardSquare(IPathMap pathMap)
        {
            Vector2 centerPoint = pathMap.ConvertPoint(boardPosition);
            this.Position = new Vector3(centerPoint.X, this.Position.Y, centerPoint.Y);
        }

        /// <summary>
        /// Esta inativos
        /// </summary>
        public bool IsIdle { get { return isIdle; } }

        /// <summary>
        /// Esta morto
        /// </summary>
        public bool IsDead { get { return isDead; } }

        public bool Playing { get { return animationPlayer.Playing; } }

        public int MovementPoints { get { return movementPoints; } set { movementPoints=value;} }

        public int CurrentMovementPoints { get; set; }
        public int Health { get; set; }
        public int AttackPower { get; set; }

        public void Stop()
        { animationPlayer.Stop(); }

        public void Play()
        { animationPlayer.Playing = true; }

        public bool Selected(Ray ray)
        {
            return CollisionDetect(ray);
        }

        /// <summary>
        /// Mover para um ponto destino
        /// </summary>
        /// <param name="destination"></param>
        public void MoveTo(Vector3 destination)
        {
            Vector3 direction =   destination - this.Position;
            direction.Normalize();
            walkdirection = new Vector3(direction.X, 0, direction.Z);
            this.destination = new Vector3(destination.X, Position.Y, destination.Z);
            this.RotationY = (float)Math.Atan2((float)direction.X, (float)direction.Z);
            Walk();
            //Quaternion q = GetRotation ( new Vector3(0, 0, 1), direction, Vector3.Up );
            //this.RotationY  = (float)Math.Atan2(2f * q.X * q.W + 2f * q.Y * q.Z, 1 - 2f * ((q.Z * q.Z) + (q.W * q.W))); 

        }
        public void MoveTo(IPathMap pathmap, Point destination)
        {
            Vector2 dest = pathmap.ConvertPoint(destination);

            Vector3 positionDest = new Vector3(dest.X, Position.Y, dest.Y);

            MoveTo(positionDest);
        }

        //public Quaternion GetRotation(Vector3 source, Vector3 dest, Vector3 up) 
        //{ 
        //    float dot = Vector3.Dot(source, dest); 
 
        //    if (Math.Abs(dot - (-1.0f)) < 0.000001f) 
        //    { 
        //        // vector a and b point exactly in the opposite direction,  
        //        // so it is a 180 degrees turn around the up-axis 
        //        return new Quaternion(up, MathHelper.ToRadians(180.0f)); 
        //    } 
        //    if (Math.Abs(dot - (1.0f)) < 0.000001f) 
        //    { 
        //        // vector a and b point exactly in the same direction 
        //        // so we return the identity quaternion 
        //        return Quaternion.Identity; 
        //    } 
 
        //    float rotAngle = (float)Math.Acos(dot); 
        //    Vector3 rotAxis = Vector3.Cross(source, dest); 
        //    rotAxis = Vector3.Normalize(rotAxis); 
        //    return Quaternion.CreateFromAxisAngle(rotAxis, rotAngle);

        // }

        //public Vector3 ToEulerAngles(Quaternion q)
        //{
        //    // Store the Euler angles in radians 
        //    Vector3 pitchYawRoll = new Vector3();

        //    double sqw = q.W * q.W;
        //    double sqx = q.X * q.X;
        //    double sqy = q.Y * q.Y;
        //    double sqz = q.Z * q.Z;

        //    // If quaternion is normalised the unit is one, otherwise it is the correction factor 
        //    double unit = sqx + sqy + sqz + sqw;
        //    double test = q.X * q.Y + q.Z * q.W;

        //    if (test > 0.4999f * unit)                              // 0.4999f OR 0.5f - EPSILON 
        //    {
        //        // Singularity at north pole 
        //        pitchYawRoll.Y = 2f * (float)Math.Atan2(q.X, q.W);  // Yaw 
        //        pitchYawRoll.X = MathHelper.Pi * 0.5f;              // Pitch 
        //        pitchYawRoll.Z = 0f;                                // Roll 
        //        return pitchYawRoll;
        //    }
        //    else if (test < -0.4999f * unit)                        // -0.4999f OR -0.5f + EPSILON 
        //    {
        //        // Singularity at south pole 
        //        pitchYawRoll.Y = -2f * (float)Math.Atan2(q.X, q.W); // Yaw 
        //        pitchYawRoll.X = -MathHelper.Pi * 0.5f;             // Pitch 
        //        pitchYawRoll.Z = 0f;                                // Roll 
        //        return pitchYawRoll;
        //    }
        //    else
        //    {
        //        pitchYawRoll.Y = (float)Math.Atan2(2f * q.X * q.W + 2f * q.Y * q.Z, 1 - 2f * (sqz + sqw));     // Yaw  
        //        pitchYawRoll.X = (float)Math.Asin(2f * (q.X * q.Z - q.W * q.Y));                               // Pitch  
        //        pitchYawRoll.Z = (float)Math.Atan2(2f * q.X * q.Y + 2f * q.Z * q.W, 1 - 2f * (sqy + sqz));     // Roll  

        //    }

        //    return pitchYawRoll;
        //}

        //public Quaternion CreateFromYawPitchRoll(float yaw, float pitch, float roll)
        //{
        //    float rollOver2 = roll * 0.5f;
        //    float sinRollOver2 = (float)Math.Sin((double)rollOver2);
        //    float cosRollOver2 = (float)Math.Cos((double)rollOver2);
        //    float pitchOver2 = pitch * 0.5f;
        //    float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
        //    float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
        //    float yawOver2 = yaw * 0.5f;
        //    float sinYawOver2 = (float)Math.Sin((double)yawOver2);
        //    float cosYawOver2 = (float)Math.Cos((double)yawOver2);
        //    Quaternion result;
        //    result.X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
        //    result.Y = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
        //    result.Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
        //    result.W = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
        //    return result;
        //}  

        /// <summary>
        /// Remove objeto
        /// </summary>
        public void SelfRemove()
        {
            this.Dispose(true);
        }

        #endregion

        #region Acoes

        public void Walk()
        {
            this.PlayAnimationClip("139_28");
            this.animationPlayer.PlaySpeed = 2;
            this.animationPlayer.Loop = true;
            state = Actions.Andando;
            isIdle = false;
        }

        public void Idle()
        {
            this.PlayAnimationClip("Take 001X");
            this.animationPlayer.PlaySpeed = 1;
            this.animationPlayer.Loop = true;
            state = Actions.Parado;
            isIdle = true;
        }

        public void GetHit()
        {
            this.PlayAnimationClip("Take 001");
            this.animationPlayer.PlaySpeed = 1;
            this.animationPlayer.Loop = false;
            state = Actions.Parado;
            isIdle = false;
        }

        public void Shoot()
        {
            // TODO: Criar animacao para acao de atirar
            this.PlayAnimationClip("Take 001XXX");
            this.animationPlayer.PlaySpeed = 1;
            this.animationPlayer.Loop = false;
            state = Actions.Atirando;
            isIdle = false;
        }

        public void LoadFrom(SoldierInfo info) 
        {
            Health = info.Health;
            AttackPower = info.AttackPower;
            CurrentMovementPoints = info.MovementPoints;
        }

        public bool InRangeOfAttack(IPathMap pathMap, ICharacter character)
        {
            Vector2 dest = pathMap.ConvertPoint(character.BoardPosition);
            Vector3 targetPosition = new Vector3(dest.X, this.Position.Y, dest.Y);
            float distancia = (targetPosition - this.Position).Length();

            return distancia <= ATTACK_RANGE;
        }

        public void Attack(IPathMap pathMap, ICharacter character)
        {
            Vector2 dest = pathMap.ConvertPoint(character.BoardPosition);

            Vector3 targetPosition = new Vector3(dest.X, this.Position.Y, dest.Y);

            Vector3 direction = targetPosition - this.Position;
            direction.Normalize();
            this.RotationY = (float)Math.Atan2((float)direction.X, (float)direction.Z);

            Shoot();
            character.TakenDamage(10);
            CurrentMovementPoints-=2;
        }

        public void TakenDamage(int damage)
        {
            this.Health -= damage;

        }

        #endregion

        #region Update

        public override void Update(GameTime gameTime)
        {
            
            base.Update(gameTime);

            switch (state)
            {
                case Actions.Parado:

                    break;
                case Actions.Andando:
                    if (Math.Abs(Vector3.Distance(destination, Position)) < velocity)
                    {
                        Position = destination;
                        Idle();
                    }
                    else
                    {
                        Position += walkdirection * velocity;
                    }
                    break;
                case Actions.Apanhando:
                case Actions.Atirando:
                    if (!animationPlayer.Playing) Idle();
                    break;
                default:
                    break;
            }


            //posicionamento da arma
            int armIndex = skinningData.BoneIndices["soldier:RightForeArm"];
            int headIndex = skinningData.BoneIndices["soldier:Head"];

            Matrix[] worldTransforms = animationPlayer.GetWorldTransforms();

            if (state == Actions.Atirando)
            {
                gunWorld = Matrix.CreateRotationX(0) *
                           Matrix.CreateRotationY((float)(-Math.PI / 2)) *
                           Matrix.CreateTranslation(0, -6, 21) *
                           Matrix.CreateScale(2.5f) * worldTransforms[headIndex];
            }
            else
            {
                gunWorld = Matrix.CreateRotationX((float)(-Math.PI / 2)) *
                            Matrix.CreateRotationY((float)(Math.PI + (Math.PI / 10))) *
                            Matrix.CreateTranslation(-22, -1, 5) *
                            Matrix.CreateScale(2.5f) * worldTransforms[armIndex];
            }

        }

        #endregion   

        #region Draw

        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
            if (canGunDraw) this.gunModel.Draw(this.gunWorld, camera.ViewMatrix, camera.ProjectionMatrix);
        }

        #endregion

        #region gun

        #endregion

        #region TurnControl
        public void NewTurn(int turnNumber)
        {
            CurrentMovementPoints = movementPoints;
        }

        #endregion

    }
}
